// basic_serie.h

#ifndef DODO_BASIC_SERIE_H
#define DODO_BASIC_SERIE_H
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <stdexcept>

#include <quigon/basic/constants.h>
#include <quigon/basic/math_utils.h>
#include <quigon/basic/basic_utils.h>

namespace quigon
{


template<class Tx, class Tval>
class Serie
{
public:
	enum interp_option { no_interp = 0, with_interp = 1 };
	typedef Tx axis_type;
	typedef Tval value_type;
	typedef typename std::map<axis_type, value_type>::const_iterator iterator;		// for using convinience.
	typedef typename std::map<axis_type, value_type>::const_iterator const_iterator; // they are actually both const_iterator

	Serie() : general_value(), value() {}
	Serie(value_type val0) : general_value(val0), value() {}
	Serie(const std::string& filename) : general_value(), value()
	{
		if (!load(filename))
			throw std::invalid_argument(std::string("Can't load serie file: ") + filename);
	}

protected:
	value_type at(const axis_type& x0, interp_option opt) const
	{
		try
		{
			if (value.empty())
				throw std::runtime_error("Serie::at() : value's empty");

			typename std::map<axis_type, value_type>::const_iterator i = value.lower_bound(x0);
			if (i == value.begin())    //most front
				return i->second;
			else if (i == value.end())   //most back
				return (--i)->second;
			else
			{
				axis_type x_after = i->first;
				value_type val_after = i->second;
				--i;
				axis_type x_before = i->first;
				value_type val_before = i->second;

				if (opt == no_interp)
					return ((x0 - x_before) < (x_after - x0)) ? val_before : val_after;
				else
					return interp(x_before, val_before, x_after, val_after, x0);
			}
		}
		catch (...)
		{
			func_err("Serie::at");
			throw;
		}
	}
public:
	size_t size() const
	{
		return value.size();
	}

	const_iterator begin() const
	{
		return value.begin();
	}
	const_iterator end() const
	{
		return value.end();
	}

	value_type interp_val(const axis_type& x0) const
	{
		if (value.empty())
			return general_value;
		else
			return at(x0, with_interp);
	}
	value_type operator [](const axis_type& x0) const
	{
		if (value.empty())
			return general_value;
		else
			return at(x0, no_interp);
	}
	value_type operator()() const
	{
		return general_value;
	}
	bool empty() const
	{
		return value.empty();
	}
	bool add_entry(const axis_type& x0, const value_type& val0)
	{
		value[x0] = val0;
		return true;
	}
	bool edit_general_value(const value_type& val0)
	{
		general_value = val0;
		return true;
	}
	bool remove_repeat()
	{
		try
		{
			if (value.empty())
				return true;

			typename std::map<axis_type, value_type>::iterator i = value.begin();
			value_type val = i->second;
			//dirty way to deal with the iterator behaviour of map.erase()
			while ((++i) != value.end())
			{
				if (i->second == val)
				{
					value.erase(i);
					--i; //what happens when erase a iterator?
				}
				else
				{
					val = i->second;
				}
			}

			return true;
		}
		catch (...)
		{
			func_err("Serie::remove_repeat");
			throw;
		}
	}
	void clear()
	{
		value.clear();
	}

	bool load(const std::string& filename, bool append = false, const std::string& time_format = "%Y-%m-%d %H:%M:%S")
	{
		try
		{
			std::ifstream file(filename.c_str());
			if (!file)
			{
				return false;
			}
			std::string templine;
			std::stringstream ss;
			imbue(ss, time_format);

			axis_type tempx;
			value_type tempval;

			std::getline(file, templine);    //comment line
			while (templine.length() == 0 || templine[0] == '#')
				std::getline(file, templine);    //get comment line until general value
			ss.clear();
			ss << templine;
			ss >> tempval;
			if (!append)
			{
				edit_general_value(tempval);
				// in append mode, general value won't be overwritten
				clear();
			}
			while (std::getline(file, templine) && !file.eof())
			{
				if (templine.length() == 0 || templine[0] == '#')
					break;
				ss.clear();
				ss << templine;
				ss >> tempx >> tempval;
				add_entry(tempx, tempval);
			}

			return true;
		}
		catch (...)
		{
			func_err("Serie::load", filename);
			throw;
		}
	}
	bool save(const std::string& filename , const std::string& note = "", const std::string& time_format = "%Y-%m-%d %H:%M:%S") const
	{
		try
		{
			std::ofstream file(filename.c_str());
			if (!file)
			{
				return false;
			}
			imbue(file, time_format);
			file << "# " << filename << std::endl;
			print_time(file, "# file updated at: ");

			if (note != "")
				file << "# " << note << std::endl;

			file << general_value << std::endl;
			for (typename std::map<axis_type, value_type>::const_iterator i = value.begin(); i != value.end() ; ++i)
			{
				file << i->first << "\t" << i->second << std::endl;
			}
			return true;
		}
		catch (...)
		{
			func_err("Serie::save", filename);
		}
	}


protected:
	value_type general_value;
	std::map<axis_type, value_type> value;
};

// to overcome the fault that strings cannot interp.
template<>
std::string Serie<std::string, std::string>::at(const std::string& x0, Serie::interp_option opt) const;
template<>
double Serie<std::string, double>::at(const std::string& x0, Serie::interp_option opt) const;

typedef Serie<boost::posix_time::ptime, double> Time_Serie;

typedef Serie<double, size_t> Lidar_Cross_Ref;   // for use in value -> index reference.
}

#endif

